#include "EasyXMen_sm2_InternalHandle.h"
#include "istd_lib.h"
#include "Crypto.h"
#include "EasyXMen_asn1.h"

#define SEED_CONST 0x1BD8C95A
extern Crypto_JobType Crypto_StoredJob;
XMen_FP_EccT Ecc256 = {
    /* Prime modulus p: Defines the finite field over which the curve is defined*/
    "FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF",
    /*Curve coefficient A: One of the coefficients of the equation defining the elliptic curve (y^2 = x^3 + Ax + B)*/
    "FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC",
    /*Curve coefficient B: The other coefficient of the equation defining the elliptic curve.*/
    "28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93",
    /*Order n of the base point G: The number of points that can be generated by repeatedly adding G to itself*/
    "FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123",
    /*X-coordinate of the base point G: A predefined point on the curve used as a generator for public keys*/
    "32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7",
    /*Y-coordinate of the base point G: The corresponding Y value for the generator point's X coordinate*/
    "BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0",
};

unsigned char sm2_par_dig[128] = {
    255, 255, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
    255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 252,
    40, 233, 250, 158, 157, 159, 94, 52, 77, 90, 158, 75, 207, 101, 9, 167,
    243, 151, 137, 245, 21, 171, 143, 146, 221, 188, 189, 65, 77, 148, 14, 147,
    50, 196, 174, 44, 31, 25, 129, 25, 95, 153, 4, 70, 106, 57, 201, 148,
    143, 227, 11, 191, 242, 102, 11, 225, 113, 90, 69, 137, 51, 76, 116, 199,
    188, 55, 54, 162, 244, 246, 119, 156, 89, 189, 206, 227, 107, 105, 33, 83,
    208, 169, 135, 124, 198, 42, 71, 64, 2, 223, 50, 229, 33, 57, 240, 160};


static int EasyXMen_SM2_Sig2Der(const uint8* r, const uint8* s, uint8** out, uint32* outlen)
{
    uint32_t len = 0;
    if ((!r) || (!s))
    {
        return 0;
    }

    // Calculate total length and encode signature components
    if (XMen_lib_asn1_sm2_integer_to_der(r, 32, NULL, &len) != 1 || XMen_lib_asn1_sm2_integer_to_der(s, 32, NULL, &len) != 1
        || XMen_lib_asn1_sm2_sequence_header_to_der(len, out, outlen) != 1 || XMen_lib_asn1_sm2_integer_to_der(r, 32, out, outlen) != 1
        || XMen_lib_asn1_sm2_integer_to_der(s, 32, out, outlen) != 1)
    {
        return -1;
    }
    return 1;
}

static int EasyXMen_SM2_Der2Sig(uint8* r_sig, uint8* s_sig, const uint8** in, uint32* inlen)
{
    int ret;
    const uint8* d;
    uint32 dlen;
    const uint8* r;
    uint32 rlen;
    const uint8* s;
    uint32 slen;

    // Parse sequence header
    if ((ret = XMen_lib_asn1_sm2_sequence_from_der(&d, &dlen, in, inlen)) != 1)
    {
        if (ret < 0)
            return ret;
    }

    // Parse signature components
    if (XMen_lib_asn1_sm2_integer_from_der(&r, &rlen, &d, &dlen) != 1 || XMen_lib_asn1_sm2_integer_from_der(&s, &slen, &d, &dlen) != 1
        || EasyXmen_SM2_Asn1_LenCmp(rlen, 32) != 1 || EasyXmen_SM2_Asn1_LenCmp(slen, 32) != 1 || EasyXmen_SM2_Asn1_LenIsZero(dlen) != 1)
    {
        return -1;
    }

    // Copy signature components to output structure
    memcpy(r_sig + 32 - rlen, r, rlen);
    memcpy(s_sig + 32 - slen, s, slen);
    return 1;
}

/**
 * @description Computes the SM3 hash for a given set of parameters including user ID, public key components (xa and ya), and a message.
 *        The function prepares a buffer with the provided data, computes an intermediate SM3 hash over this buffer,
 *        combines it with the message, and finally computes the SM3 hash over the combination.
 * @param[in] userid : Pointer to the user ID byte array.
 * @param[in] userid_len : Length of the user ID byte array in bytes.
 * @param[in] xa : Pointer to the X component of the public key byte array.
 * @param[in] xa_len : Length of the X component byte array in bytes. Must not exceed 32 bytes.
 * @param[in] ya : Pointer to the Y component of the public key byte array.
 * @param[in] ya_len : Length of the Y component byte array in bytes. Must not exceed 32 bytes.
 * @param[in] msg : Pointer to the message byte array to be hashed.
 * @param[in] msg_len : Length of the message byte array in bytes.
 * @param[out] e : Pointer to the output buffer where the resulting hash will be stored.
 * @return int
 * @retval 0 : Hash computation was successful.
 * @retval -1 : An error occurred, either due to invalid input lengths or memory allocation failure.
 */
int EasyXMen_sm3Hash(unsigned char *userid, int userid_len, unsigned char *xa, int xa_len, unsigned char *ya, int ya_len, unsigned char *msg, int msg_len, unsigned char *e)
{
    unsigned char *buf;
    int userid_bitlen;

    if ((xa_len > 32) || (ya_len > 32))
        return -1;

    buf = (unsigned char *)malloc(2 + userid_len + 128 + 32 + 32);
    if (buf == NULL)
        return -1;

    userid_bitlen = userid_len << 3;
    buf[0] = (userid_bitlen >> 8) & 0xFF;
    buf[1] = userid_bitlen & 0xFF;

    ILib_memcpy(buf + 2, userid, userid_len);
    ILib_memcpy(buf + 2 + userid_len, sm2_par_dig, 128);

    ILib_memset(buf + 2 + userid_len + 128, 0, 64);
    ILib_memcpy(buf + 2 + userid_len + 128 + 32 - xa_len, xa, 32);
    ILib_memcpy(buf + 2 + userid_len + 128 + 32 + 32 - ya_len, ya, 32);

    EasyXMen_sm3(buf, 2 + userid_len + 128 + 32 + 32, e);
    free(buf);

    buf = (unsigned char *)malloc(msg_len + 32);
    if (buf == NULL)
        return -1;

    ILib_memcpy(buf, e, 32);
    ILib_memcpy(buf + 32, msg, msg_len);
    EasyXMen_sm3(buf, 32 + msg_len, e);

    free(buf);

    return 0;
}

/**
 * @description Performs the SM2 digital signature generation on a given hash using the provided private key.
 *        The function initializes necessary elliptic curve parameters, generates a random number,
 *        and computes the signature (r, s) pair for the input hash value. It ensures that the generated
 *        values are valid and re-executes the signing process if invalid values are obtained.
 * @param[in]  hash      : Pointer to the hash byte array to be signed.
 * @param[in]  hashlen   : Length of the hash byte array in bytes.
 * @param[in]  privkey   : Pointer to the private key byte array.
 * @param[in]  privkeylen: Length of the private key byte array in bytes.
 * @param[out] cr        : Pointer to the output buffer where the 'r' component of the signature will be stored.
 * @param[out] rlen      : Pointer to an integer where the length of the 'r' component will be stored.
 * @param[out] cs        : Pointer to the output buffer where the 's' component of the signature will be stored.
 * @param[out] slen      : Pointer to an integer where the length of the 's' component will be stored.
 * @return void
 */
void EasyXMen_sm2_sign(unsigned char *hash, int hashlen, unsigned char *privkey, int privkeylen, unsigned char *cr, int *rlen, unsigned char *cs, int *slen)
{
    XMen_FP_EccT *cfig = &Ecc256;
    XMen_BigTPtr e, r, s, k;
    XMen_BigTPtr a, b, p, n, x, y, key1;
    XMen_ECurvePointT *g;
    XMen_SM2Type *mip = XMen_Mirsys(20, 0);

    mip->IOBASE = 16;
    e = XMen_Mirvar(0);
    r = XMen_Mirvar(0);
    s = XMen_Mirvar(0);
    k = XMen_Mirvar(0);

    p = XMen_Mirvar(0);
    a = XMen_Mirvar(0);
    b = XMen_Mirvar(0);
    n = XMen_Mirvar(0);
    x = XMen_Mirvar(0);
    y = XMen_Mirvar(0);

    key1 = XMen_Mirvar(0);
    XMen_Bytes2Big(privkeylen, (char *)privkey, key1);

    XMen_Cinstr(p, cfig->p);
    XMen_Cinstr(a, cfig->a);
    XMen_Cinstr(b, cfig->b);
    XMen_Cinstr(n, cfig->n);
    XMen_Cinstr(x, cfig->x);
    XMen_Cinstr(y, cfig->y);
    XMen_EcurveInit(a, b, p, XMEN_PROJECTIVE);
    g = XMen_EpointInit();

    XMen_EpointSet(x, y, 0, g);

    XMen_Bytes2Big(hashlen, (char *)hash, e);
    XMen_Irand(time(NULL) + SEED_CONST);

sm2_sign_again:
    do
    {
        XMen_BigRand(n, k);
    } while (k->len == 0);

    XMen_EcurveMult(k, g, g);
    XMen_EpointGet(g, r, r);

    XMen_Add(e, r, r);
    XMen_Divide(r, n, n);

    if (r->len == 0)
        goto sm2_sign_again;

    XMen_Add(r, k, a);
    if (XMen_Compare(a, n) == 0)
        goto sm2_sign_again;

    XMen_Incr(key1, 1, b);
    XMen_Xgcd(b, n, b, b, b);

    XMen_Multiply(r, key1, a);
    XMen_Divide(a, n, n);

    if (XMen_Compare(k, a) >= 0)
    {
        XMen_Subtract(k, a, a);
    }
    else
    {
        XMen_Subtract(n, a, a);
        XMen_Add(k, a, a);
    }

    XMen_Mad(a, b, a, n, n, s);

    if (s->len == 0)
        goto sm2_sign_again;

    *rlen = XMen_Big2Bytes(0, r, (char *)cr, FALSE);
    *slen = XMen_Big2Bytes(0, s, (char *)cs, FALSE);

    XMen_Mirkill(e);
    XMen_Mirkill(r);
    XMen_Mirkill(s);
    XMen_Mirkill(k);
    XMen_Mirkill(p);
    XMen_Mirkill(a);
    XMen_Mirkill(b);
    XMen_Mirkill(n);
    XMen_Mirkill(x);
    XMen_Mirkill(y);
    XMen_Mirkill(key1);
    XMen_EpointFree(g);
    XMen_Mirexit();
}

/**
 * @description Verifies the SM2 digital signature for a given hash using the provided public key components and signature values.
 *        The function checks if the signature (r, s) pair is valid for the input hash value based on the specified elliptic curve parameters.
 *        It ensures that all intermediate values are within expected bounds and performs necessary computations to validate the signature.
 * @param[in]  hash   : Pointer to the hash byte array that was signed.
 * @param[in]  hashlen: Length of the hash byte array in bytes.
 * @param[in]  cr     : Pointer to the 'r' component of the signature byte array.
 * @param[in]  rlen   : Length of the 'r' component byte array in bytes.
 * @param[in]  cs     : Pointer to the 's' component of the signature byte array.
 * @param[in]  slen   : Length of the 's' component byte array in bytes.
 * @param[in]  wx     : Pointer to the X component of the public key byte array.
 * @param[in]  wxlen  : Length of the X component byte array in bytes.
 * @param[in]  wy     : Pointer to the Y component of the public key byte array.
 * @param[in]  wylen  : Length of the Y component byte array in bytes.
 * @return int
 * @retval 0 : The signature is valid for the given hash and public key.
 * @retval -1: The signature verification failed or an error occurred.

 */
int EasyXMen_sm2_verify(unsigned char *hash, int hashlen, unsigned char *cr, int rlen, unsigned char *cs, int slen, unsigned char *wx, int wxlen, unsigned char *wy, int wylen)
{

    XMen_FP_EccT *cfig = &Ecc256;
    XMen_BigTPtr e, r, s, v;
    XMen_BigTPtr a, b, p, n, x, y;
    XMen_ECurvePointT *g, *w;
    int ret = -1;
    XMen_SM2Type *mip = XMen_Mirsys(20, 0);

    mip->IOBASE = 16;
    p = XMen_Mirvar(0);
    a = XMen_Mirvar(0);
    b = XMen_Mirvar(0);
    n = XMen_Mirvar(0);
    x = XMen_Mirvar(0);
    y = XMen_Mirvar(0);

    e = XMen_Mirvar(0);
    r = XMen_Mirvar(0);
    s = XMen_Mirvar(0);
    v = XMen_Mirvar(0);

    XMen_Cinstr(p, cfig->p);
    XMen_Cinstr(a, cfig->a);
    XMen_Cinstr(b, cfig->b);
    XMen_Cinstr(n, cfig->n);
    XMen_Cinstr(x, cfig->x);
    XMen_Cinstr(y, cfig->y);

    XMen_EcurveInit(a, b, p, XMEN_PROJECTIVE);
    g = XMen_EpointInit();
    w = XMen_EpointInit();
    XMen_EpointSet(x, y, 0, g);
    XMen_Bytes2Big(wxlen, (char *)wx, x);
    XMen_Bytes2Big(wylen, (char *)wy, y);
    if (!XMen_EpointSet(x, y, 0, w))
        goto exit_sm2_verify;

    XMen_Bytes2Big(hashlen, (char *)hash, e);
    XMen_Bytes2Big(rlen, (char *)cr, r);
    XMen_Bytes2Big(slen, (char *)cs, s);

    if ((XMen_Compare(r, n) >= 0) || (r->len == 0))
        goto exit_sm2_verify;

    if ((XMen_Compare(s, n) >= 0) || (s->len == 0))
        goto exit_sm2_verify;

    XMen_Add(s, r, a);
    XMen_Divide(a, n, n);
    if (a->len == 0)
        goto exit_sm2_verify;

    XMen_EcurveMult2(s, g, a, w, g);
    XMen_EpointGet(g, v, v);

    XMen_Add(v, e, v);
    XMen_Divide(v, n, n);

    if (XMen_Compare(v, r) == 0)
        ret = 0;

exit_sm2_verify:

    XMen_Mirkill(r);
    XMen_Mirkill(s);
    XMen_Mirkill(v);
    XMen_Mirkill(e);
    XMen_Mirkill(a);
    XMen_Mirkill(b);
    XMen_Mirkill(p);
    XMen_Mirkill(n);
    XMen_Mirkill(x);
    XMen_Mirkill(y);
    XMen_EpointFree(g);
    XMen_EpointFree(w);
    XMen_Mirexit();

    return ret;
}

void Crypto_SM2_signProcess()
{

    unsigned char userid[] = "1234567812345678";
    int userid_len = sizeof(userid) - 1;

    /*Declare two unsigned char arrays named r_sm2 and s_sm2 that will be used to
     *store the R and S values obtained during the signing process.*/
    unsigned char r_sm2[32];
    unsigned char s_sm2[32];
    int rlen, slen;

    unsigned char kA_sm2[32];
    uint32 kA_sm2_len = 32;

    unsigned char xa[32];
    uint32 xa_len = sizeof(xa);

    unsigned char ya[32];
    uint32 ya_len = sizeof(ya);

    unsigned char e[32];

    /*Input Data.*/
    uint32 ilen = Crypto_StoredJob.jobPrimitiveInputOutput.inputLength;
    uint8 *input = (uint8 *)(Crypto_StoredJob.jobPrimitiveInputOutput.inputPtr);
    uint8 *output = (uint8 *)(Crypto_StoredJob.jobPrimitiveInputOutput.outputPtr);

    Crypto_KeyElementGet(0, 1, kA_sm2, &kA_sm2_len);
    Crypto_KeyElementGet(1, 1, xa, &xa_len);
    Crypto_KeyElementGet(2, 3, ya, &ya_len);

    EasyXMen_sm3Hash(userid, userid_len, xa, xa_len, ya, ya_len, input, ilen, e);
    EasyXMen_sm2_sign(e, 32, kA_sm2, 32, r_sm2, &rlen, s_sm2, &slen);
    uint32 *siglen = (Crypto_StoredJob.jobPrimitiveInputOutput.outputLengthPtr);
    EasyXMen_SM2_Sig2Der(r_sm2, s_sm2, &output, siglen);
}

void Crypto_SM2_verifyProcess()
{
    int ret;
    unsigned char userid[] = "1234567812345678";
    int userid_len = sizeof(userid) - 1;

    /*Declare two unsigned char arrays named r_sm2 and s_sm2 that will be used to
     *store the R and S values obtained during the signing process.*/
    unsigned char r_sm2[32];
    unsigned char s_sm2[32];

    unsigned char kA_sm2[32];
    uint32 kA_sm2_len = 32;

    unsigned char xa[32];
    uint32 xa_len = sizeof(xa);

    unsigned char ya[32];
    uint32 ya_len = sizeof(ya);

    unsigned char e[32];

    uint32 ilen = Crypto_StoredJob.jobPrimitiveInputOutput.inputLength;
    const uint8 *input = (uint8 *)(Crypto_StoredJob.jobPrimitiveInputOutput.inputPtr);
    const uint8* sig_result = Crypto_StoredJob.jobPrimitiveInputOutput.secondaryInputPtr;
    uint32 siglen = Crypto_StoredJob.jobPrimitiveInputOutput.secondaryInputLength;

    memset(r_sm2, 0, 32);
    memset(s_sm2, 0, 32);
    EasyXMen_SM2_Der2Sig(r_sm2, s_sm2, &sig_result, &siglen);

    Crypto_KeyElementGet(0, 1, kA_sm2, &kA_sm2_len);
    Crypto_KeyElementGet(1, 1, xa, &xa_len);
    Crypto_KeyElementGet(2, 3, ya, &ya_len);

    EasyXMen_sm3Hash(userid, userid_len, xa, xa_len, ya, ya_len, input, ilen, e);

    ret = EasyXMen_sm2_verify(e, 32, r_sm2, 32, s_sm2, 32, xa, 32, ya, 32);
    if (ret == 0)
    {
        *Crypto_StoredJob.jobPrimitiveInputOutput.verifyPtr = 0;
    }
    else
    {
        *Crypto_StoredJob.jobPrimitiveInputOutput.verifyPtr = 1;
    }
}
